//
//  BMPhoneStatus.m
//  snomPhone
//
//  Created by Daniel Hazelbaker on 11/2/08.
//  Copyright 2008 Blue Box Moon. All rights reserved.
//

#import "BMPhoneStatus.h"


#define PHONEICON_SPACING   5.0f

@implementation BMPhoneStatus


//
// Initialize this phone status object. Create a new status menu item for use and
// do some basic population.
//
- (id)init
{
    return [self initOffline:NO];
}


//
// Initialize this phone status object. Create a new status menu item for use and
// do some basic population. We are created with the given offline status.
//
- (id)initOffline:(BOOL)status
{
    if ([super init] == nil)
	return nil;

    //
    // Initialize all the phone status icons.
    //
    [self initializeStatusIcons];

    //
    // Create the status item.
    //
    statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
    [statusItem setHighlightMode:YES];

    //
    // Initialize some basic values we need.
    //
    phoneMiddleImage = [[NSImage imageNamed:@"phoneMiddle"] retain];
    [phoneMiddleImage setScalesWhenResized:YES];
    [phoneMiddleImage setSize:NSMakeSize(1, 18.0f)];
    offline = status;

    //
    // Put in the basic phone icon.
    //
    statusIcons = [[NSMutableArray alloc] initWithObjects:@"phoneLeft", @"phoneText", @"phoneRight", nil];
    [self refreshIconDisplay];

    return self;
}


//
// Initialize this class. Setup any initial information we need, status icons.
//
- (void)initializeStatusIcons
{
    NSMutableArray	*array;
    NSImage		*image;
    NSArray		*images;
    float		ratio;
    int			i;


    array = [[NSMutableArray alloc] init];

    //
    // Basic phone icon, always on.
    //
    image = [NSImage imageNamed:@"phoneLeft"];
    [image setScalesWhenResized:YES];
    ratio = ([image size].width / [image size].height);
    [image setSize:NSMakeSize(rint(18.0f * ratio), 18.0f)];
    [array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
			@"phoneLeft", PhoneStatusIcon_Name,
			[NSNumber numberWithInt:0], PhoneStatusIcon_Priority,
			image, PhoneStatusIcon_Image,
			nil]];

    //
    // Missed calls.
    //
    [array addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
			PhoneStatus_MissedCalls, PhoneStatusIcon_Name,
			[NSNumber numberWithInt:10], PhoneStatusIcon_Priority,
			nil]];

    //
    // Do-Not-Disturb.
    //
    image = [[[NSImage imageNamed:@"dnd"] copy] autorelease];
    ratio = ([image size].width / [image size].height);
    [image setScalesWhenResized:YES];
    [image setSize:NSMakeSize(rint(12.0f * ratio), 12.0f)];
    [array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
			PhoneStatus_DND, PhoneStatusIcon_Name,
			@"You will not receive calls (DND).", PhoneStatusIcon_Description,
			[NSNumber numberWithInt:80], PhoneStatusIcon_Priority,
			image, PhoneStatusIcon_Image,
			nil]];

    //
    // Progress Indicator (i.e., working, etc.).
    //
    images = [NSArray arrayWithObjects:
		[NSImage imageNamed:@"progress01"], [NSImage imageNamed:@"progress02"],
		[NSImage imageNamed:@"progress03"], [NSImage imageNamed:@"progress04"],
		[NSImage imageNamed:@"progress05"], [NSImage imageNamed:@"progress06"],
		[NSImage imageNamed:@"progress07"], [NSImage imageNamed:@"progress08"],
		[NSImage imageNamed:@"progress09"], [NSImage imageNamed:@"progress10"],
		[NSImage imageNamed:@"progress11"], [NSImage imageNamed:@"progress12"],
		nil];
    for (i = 0; i < [images count]; i++) {
	[[images objectAtIndex:i] setScalesWhenResized:YES];
	[[images objectAtIndex:i] setSize:NSMakeSize(16, 16)];
    }
    [array addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
			@"progress", PhoneStatusIcon_Name,
			@"Communicating with the phone.", PhoneStatusIcon_Description,
			[NSNumber numberWithInt:99], PhoneStatusIcon_Priority,
			[images objectAtIndex:0], PhoneStatusIcon_Image,
			images, PhoneStatusIcon_Images,
			nil]];
    progressTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0f / 12.0f) target:self selector:@selector(progressTimer:) userInfo:[array lastObject] repeats:YES];

    //
    // End of the basic phone icon.
    //
    [array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
		      @"phoneText", PhoneStatusIcon_Name,
		      [NSNumber numberWithInt:0], PhoneStatusIcon_Priority,
		      nil]];

    //
    // End of the basic phone icon.
    //
    image = [NSImage imageNamed:@"phoneRight"];
    [image setScalesWhenResized:YES];
    ratio = ([image size].width / [image size].height);
    [image setSize:NSMakeSize(rint(18.0f * ratio), 18.0f)];
    [array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
		      @"phoneRight", PhoneStatusIcon_Name,
		      [NSNumber numberWithInt:0], PhoneStatusIcon_Priority,
		      image, PhoneStatusIcon_Image,
		      nil]];
    
    phoneStatusIcons = array;
}


//
// Set the phone text to be displayed.
//
- (void)setPhoneText:(NSString *)text
{
    [phoneText autorelease];
    phoneText = [text retain];
    
    [self refreshIconDisplay];
}


//
// Retrieve the phone text being displayed.
//
- (NSString *)phoneText
{
    return phoneText;
}


//
// Set the menu used in this status item.
//
- (void)setMenu:(NSMenu *)menu
{
    [statusItem setMenu:menu];
}


//
// Retrieve the menu currently used in this status item.
//
- (NSMenu *)menu
{
    return [statusItem menu];
}


//
// Set the phone icon to the given status.
//
- (void)setPhoneStatus:(BOOL)status forIcon:(NSString *)icon
{
    if (status == [self phoneStatusForIcon:icon])
	return;

    if (status)
	[statusIcons addObject:icon];
    else
	[statusIcons removeObject:icon];

    [self refreshIconDisplay];
}


//
// Retrieve the status of the given phone icon.
//
- (BOOL)phoneStatusForIcon:(NSString *)icon
{
    return [statusIcons containsObject:icon];
}


//
// Request that the main thread refresh the icon display.
//
- (void)refreshIconDisplay
{
    [self performSelectorOnMainThread:@selector(mainRefreshIconDisplay:) withObject:nil waitUntilDone:YES];
}


//
// Refresh the display of icons. Do any sorting and resizing we need.
//
- (void)mainRefreshIconDisplay:(id)ignored
{
    NSAutoreleasePool   *pool;
    NSMutableString *tooltip;
    NSDictionary    *icon;
    NSImage	    *image, *finalImage;
    NSRect	    frame, fillFrame;
    float	    x = 5.0f, y;
    int		    i, n;


    pool = [[NSAutoreleasePool alloc] init];

    //
    // Walk all the possible icons and determine the size needed.
    //
    tooltip = [NSMutableString string];
    finalImage = [[NSImage alloc] initWithSize:NSMakeSize(512, 22)];
    [finalImage lockFocus];
    for (i = 0; i < [phoneStatusIcons count]; i++) {
	icon = [phoneStatusIcons objectAtIndex:i];
	image = [icon objectForKey:PhoneStatusIcon_Image];

	//
	// Check if this icon is active.
	//
	if ([statusIcons containsObject:[icon objectForKey:PhoneStatusIcon_Name]] == NO)
	    continue;

	//
	// Prepare icon frame.
	//
	if (image != nil)
	{
	    frame.size = [image size];
	    frame.origin = NSMakePoint(0, 0);
	}
	else
	{
	    if ([phoneText length] == 0)
		continue;
	    
	    frame.size = [phoneText sizeWithAttributes:nil];
	    frame.origin = NSMakePoint(0, 0);
	}

	//
	// Draw the background image.
	//
	if (i > 0 && (i + 1) < [phoneStatusIcons count]) {
	    fillFrame.size = [phoneMiddleImage size];
	    fillFrame.origin = NSMakePoint(0, 0);
	    y = floorf((22 - fillFrame.size.height) / 2);
	    for (n = 0; n < (frame.size.width + PHONEICON_SPACING); n++)
		[phoneMiddleImage drawAtPoint:NSMakePoint((x + n), y) fromRect:fillFrame operation:NSCompositeSourceOver fraction:1.0f];

	    x += PHONEICON_SPACING;
	}

	//
	// Draw the actual icon image.
	//
	if (image != nil)
	{
	    [image drawAtPoint:NSMakePoint(x, floorf((22 - frame.size.height) / 2)) fromRect:frame operation:NSCompositeSourceOver fraction:1.0f];
	}
	else
	{
	    [phoneText drawAtPoint:NSMakePoint(x, floorf((22 - frame.size.height) / 2)) withAttributes:nil];
	}
	x += frame.size.width;

	//
	// Append the tooltip.
	//
	if ([icon objectForKey:PhoneStatusIcon_Description] != nil) {
	    if ([tooltip length] > 0)
		[tooltip appendString:@"\n"];
	    [tooltip appendString:[icon objectForKey:PhoneStatusIcon_Description]];
	}
    }
    [finalImage unlockFocus];

    //
    // Resize the image to fit the final size.
    //
    image = [[[NSImage alloc] initWithSize:NSMakeSize(x, 22)] autorelease];
    [image lockFocus];
    [finalImage drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, x, 22) operation:NSCompositeCopy fraction:1.0];
    [image unlockFocus];
    [finalImage release];
    finalImage = image;

    //
    // If we are offline then gray out the image.
    //
    if (offline) {
        finalImage = [self grayscaleImage:finalImage];
        tooltip = [NSMutableString stringWithString:@"Phone is offline"];
    }
    
    //
    // Update the image.
    //
    [statusItem setImage:finalImage];

    //
    // Update the tooltip.
    //
    if ([tooltip length] > 0)
	[statusItem setToolTip:tooltip];
    else
	[statusItem setToolTip:@"Phone is ready."];
    
    [pool drain];
}


//
// Convert the given image into a grayscale version.
//
- (NSImage *)grayscaleImage:(NSImage *)image
{
    NSBitmapImageRep	*bitmap;
    NSImage		*newImage;
    NSColor		*color;
    float		white;
    int			x, y;


    //
    // Create the bitmap image representation.
    //
    bitmap = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];

    //
    // Do final conversion to grayscale.
    //
    for (y = 0; y < [bitmap pixelsHigh]; y++) {
	for (x = 0; x < [bitmap pixelsWide]; x++) {
	    color = [bitmap colorAtX:x y:y];
	    white = (([color redComponent] + [color greenComponent] + [color blueComponent]) / 3);
	    color = [NSColor colorWithCalibratedRed:white green:white blue:white alpha:[color alphaComponent]];
	    [bitmap setColor:color atX:x y:y];
	}
    }

    //
    // Store the bitmap representation and return the image.
    //
    newImage = [[NSImage alloc] init];
    [newImage addRepresentation:bitmap];

    return [newImage autorelease];
}


//
// Request the main thread update the number of missed calls.
//
- (void)setMissedCallCount:(int)missed
{
    [self performSelectorOnMainThread:@selector(mainSetMissedCallCount:) withObject:[NSNumber numberWithInt:missed] waitUntilDone:YES];
}


//
// Set the number of missed calls for this phone.
//
- (void)mainSetMissedCallCount:(NSNumber *)missedCount
{
    NSMutableDictionary	*icon;
    NSAttributedString	*missedString;
    NSDictionary	*attributes;
    NSImage		*image;
    NSPoint		pt;
    NSSize		size;
    int			missed = [missedCount intValue];


    //
    // If we have no missed calls, no worries!
    //
    if (missed == 0) {
	[self setPhoneStatus:NO forIcon:PhoneStatus_MissedCalls];

	return;
    }

    //
    // Setup drawing attributes.
    //
    attributes = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSFont labelFontOfSize:10], NSFontAttributeName,
			[NSColor whiteColor], NSForegroundColorAttributeName,
			nil];

    //
    // Generate the string and find the right badge.
    //
    missedString = [[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", missed] attributes:attributes] autorelease];
    if ([missedString length] == 1)
	image = [NSImage imageNamed:@"badge1&2"];
    else if ([missedString length] == 2)
	image = [NSImage imageNamed:@"badge3"];
    else
	image = [NSImage imageNamed:@"badge5"];
    image = [[image copy] autorelease];

    //
    // Resize the image.
    //
    [image setScalesWhenResized:YES];
    [image setSize:NSMakeSize(rint(18.0f * ([image size].width / [image size].height)), 18.0f)];

    //
    // Find the center position.
    //
    size = [missedString size];
    pt.x = floorf(([image size].width - size.width) / 2.0f);
    pt.y = floorf(([image size].height - size.height) / 2.0f);

    //
    // Draw the string into the image.
    //
    [image lockFocus];
    [missedString drawAtPoint:pt];
    [image unlockFocus];

    //
    // Find the icon to update.
    //
    icon = [[phoneStatusIcons filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K == %@", PhoneStatusIcon_Name, PhoneStatus_MissedCalls]] objectAtIndex:0];
    [icon setValue:image forKey:PhoneStatusIcon_Image];
    [icon setValue:[NSString stringWithFormat:@"You have %d missed call%s.", missed, (missed == 1 ? "" : "s")] forKey:PhoneStatusIcon_Description];

    //
    // Show or update.
    //
    if ([self phoneStatusForIcon:PhoneStatus_MissedCalls] == YES)
	[self refreshIconDisplay];
    else
	[self setPhoneStatus:YES forIcon:PhoneStatus_MissedCalls];
}


//
// Show the progress indicator and make sure it is spinning.
//
- (void)startWorking
{
    [progressTimer setFireDate:[NSDate date]];
    [self setPhoneStatus:YES forIcon:PhoneStatus_Progress];
}


//
// Stop the progress indicator from spinning and hide it.
//
- (void)stopWorking
{
    [self setPhoneStatus:NO forIcon:PhoneStatus_Progress];
    [progressTimer setFireDate:[NSDate distantFuture]];
}


//
// Timer to step the progress indicator. Move it along...
//
- (void)progressTimer:(NSTimer *)timer
{
    NSMutableDictionary	*icon;
    NSArray		*images;
    int			index;


    icon = [timer userInfo];
    images = [icon valueForKey:PhoneStatusIcon_Images];
    index = [images indexOfObjectIdenticalTo:[icon valueForKey:PhoneStatusIcon_Image]];
    if ((index + 1) >= [images count])
	index = 0;
    else
	index += 1;

    [icon setValue:[images objectAtIndex:index] forKey:PhoneStatusIcon_Image];

    [self refreshIconDisplay];
}


//
// Set this phone status to be offline, gray out the icon.
//
- (void)setOffline:(BOOL)state
{
    offline = state;
    [self refreshIconDisplay];
}


//
// Retrieve the current offline state of this phone status.
//
- (BOOL)isOffline
{
    return offline;
}


@end
